package models;

import static util.FindFoodUtil.empty;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import play.Logger;
import play.cache.Cache;
import util.FindFoodUtil;
import util.MapAccessibleHelper;
import util.MapAccessibleHelper.MapAccessible;
import util.MapAccessibleHelper.MappedField;

import com.flaptor.indextank.apiclient.IndexDoesNotExistException;
import com.flaptor.indextank.apiclient.InvalidSyntaxException;
import com.flaptor.indextank.apiclient.IndexTankClient.Index;
import com.flaptor.indextank.apiclient.IndexTankClient.Query;
import com.flaptor.indextank.apiclient.IndexTankClient.SearchResults;
import com.javadocmd.simplelatlng.LatLng;

public class Restaurant implements MapAccessible, Serializable {

	public static final String[] fieldNames = new String[]{"docid", "name",
			"address", "city", "state", "postcode", "telephone", "fax",
			"category", "website", "latitude", "longitude"};

	private static Map<String, MappedField> mappedFields;
	static {
		try {
			mappedFields = MapAccessibleHelper.discoverFields(Restaurant.class,
					Restaurant.fieldNames);
		} catch (Exception e) {
			throw new RuntimeException("Cannot initialize Restaurant fields.");
		}
	}

	/**
	 * Generates an IndexTank query for the given parameters.
	 * 
	 * @param keywords general search terms.
	 * @param category the category to restrict the search to. May be null.
	 * @param coordinate the coordinates to center the search upon. May be null.
	 * @return a Query object for IndexTank.
	 */
	public static Query queryFor(String keywords, Category category,
			LatLng coordinate) {
		Query query = null;
		if (keywords != null) {
			query = Query.forString(keywords.trim());
		} else {
			query = Query.forString("");
		}

		if (coordinate != null) {
			query.withScoringFunction(1);
			query.withQueryVariable(0, (float) coordinate.getLatitude());
			query.withQueryVariable(1, (float) coordinate.getLongitude());
		}

		if (category != null) {
			Map<String, List<String>> filters = new HashMap<String, List<String>>();

			List<String> categoryFilter = new ArrayList<String>();
			categoryFilter.add(category.getName());
			filters.put("category" + category.getDepth(), categoryFilter);

			query.withCategoryFilters(filters);
		}

		query.withFetchFields(Restaurant.fieldNames);
		return query;
	}

	/**
	 * Fetches Restaurant details from IndexTank.
	 * 
	 * @param index the index to search.
	 * @param docid the ID of the document.
	 * @return the Restaurant object or null if no such document exists.
	 * @throws IOException if the document cannot be retrieved.
	 */
	public static Restaurant get(Index index, String docid) throws IOException {
		if (empty(docid))
			return null;

		Restaurant r = Cache.get(docid, Restaurant.class);
		if (r == null) {
			try {
				Query query = Query.forString(docid).withFetchFields(
						Restaurant.fieldNames);
				SearchResults results = index.search(query);
				if (results.results.size() > 0) {
					r = new Restaurant(results.results.get(0));
					Cache.set(docid, r);
				}
			} catch (InvalidSyntaxException e) {
				Logger.error(e, "Invalid single doc search syntax.");
			}
		}
		return r;
	}

	private String docid;
	private String name;
	private String address;
	private String city;
	private String state;
	private String postcode;
	private String telephone;
	private String fax;
	private String category;
	private String website;
	private String latitude;
	private String longitude;

	private Category categoryObject;

	public Restaurant() {

	}

	public Restaurant(Map<String, Object> values) {
		MapAccessibleHelper.setFields(this, values);
	}

	@Override
	public Map<String, MappedField> getMappedFields() {
		return Restaurant.mappedFields;
	}

	/**
	 * Transmits this document to IndexTank.
	 */
	public void updateIndexTank(Index index) throws IOException,
			IndexDoesNotExistException {
		Map<String, String> fields = FindFoodUtil.retypeMap(MapAccessibleHelper
				.getFields(this));
		fields.remove("docid");

		fields.put("text", String.format("%s %s %s %s %s %s %s %s %s %s %s %s",
				docid, name, address, city, state, postcode, telephone, fax,
				category, website, latitude, longitude));

		Map<String, String> categories = new HashMap<String, String>();
		categories.put("citystate", city + " " + state);
		String[] cats = Category.splitCategory(category);
		switch (cats.length) {
			case 3 :
				categories.put("category3", cats[2]);
			case 2 :
				categories.put("category2", cats[1]);
			case 1 :
				categories.put("category1", cats[0]);
			default :
				break;
		}

		Map<Integer, Float> variables = new HashMap<Integer, Float>();
		if (!empty(latitude) && !empty(longitude)) {
			variables.put(0, Float.parseFloat(latitude));
			variables.put(1, Float.parseFloat(longitude));
		}

		index.addDocument(docid, fields, variables, categories);
	}

	public String getFullAddress() {
		return String.format("%s %s, %s %s", address, city, state, postcode);
	}

	public String getDocid() {
		return docid;
	}

	public void setDocid(String docid) {
		this.docid = docid;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String city) {
		this.city = city;
	}

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}

	public String getPostcode() {
		return postcode;
	}

	public void setPostcode(String postcode) {
		this.postcode = postcode;
	}

	public String getTelephone() {
		return telephone;
	}

	public void setTelephone(String telephone) {
		this.telephone = telephone;
	}

	public String getFax() {
		return fax;
	}

	public void setFax(String fax) {
		this.fax = fax;
	}

	public String getCategory() {
		return category;
	}

	public void setCategory(String categoryString) {
		this.category = categoryString;
		this.categoryObject = Category.getByString(categoryString);
	}

	public Category getCategoryObject() {
		return categoryObject;
	}

	public void setCategoryObject(Category category) {
		this.category = category.toString();
		this.categoryObject = category;
	}

	public String getWebsite() {
		return website;
	}

	public void setWebsite(String website) {
		this.website = website;
	}

	public String getLatitude() {
		return latitude;
	}

	public void setLatitude(String latitude) {
		this.latitude = latitude;
	}

	public String getLongitude() {
		return longitude;
	}

	public void setLongitude(String longitude) {
		this.longitude = longitude;
	}
}
